//
//  TBUSBPrinterDNP.m
//  PrinterSniff
//
//  Created by atomicpepper on 2/11/13.
//
//

#import "TBUSBPrinterDNP.h"
//#define DNP_PRINTER_UTILITY

NSString * ATMPPR_DNP_USB_ISerialisSet = @"ATMPPR_DNP_USB_ISerialisSet";
NSString * ATMPPR_DNP_USB_SuppressPowerCycleAlert = @"ATMPPR_DNP_USB_SuppressPowerCycleAlert";

NSString * cFirmwareFilenames = @"firmwareFilenames";
NSString * cFirmwareFileHashes = @"firmwareFileHash";
NSString* const deviceFirmwareUpdateState = @"deviceFirmwareStatus";

@implementation TBUSBPrinterDNP {
    BOOL _customerDidAgree;
}
+(NSIndexSet *)supportedVendorIDs
{
    NSMutableIndexSet * set = [NSMutableIndexSet indexSet];
    [set addIndex:k_dnp_vendor_id_DS40];  // older DNP
    [set addIndex:k_dnp_vendor_id_DS620];  // DNP 620 and DNP 820
    
    return set.copy;
}
+(NSIndexSet *)supportedProductIDs
{
    NSMutableIndexSet * set = [NSMutableIndexSet indexSet];

#define _DNP_FIRMWARE_UPDATER
#ifdef _DNP_MODEL_DS40_
    [set addIndex:k_dnp_product_id_DS40];
#elif _DNP_MODEL_DS80_
    [set addIndex:k_dnp_product_id_DS80];
#elif _DNP_MODEL_DS80D_
    [set addIndex:k_dnp_product_id_DS80D];
#elif _DNP_MODEL_RX1_
    [set addIndex:k_dnp_product_id_RX1];
#elif _DNP_MODEL_DS620_
    [set addIndex:k_dnp_product_id_DS620];
#elif _DNP_MODEL_DS820_
    [set addIndex:k_dnp_product_id_DS820];
#elif DNP_PRINTER_UTILITY
    [set addIndex:k_dnp_product_id_DS40];
    [set addIndex:k_dnp_product_id_DS80];
    [set addIndex:k_dnp_product_id_DS80D];
    [set addIndex:k_dnp_product_id_RX1];
#else /* iSerial Tool */
 #undef  _DNP_FIRMWARE_UPDATER
#define _DNP_ISERIAL_TOOL
    [set addIndex:k_dnp_product_id_RX1];
    [set addIndex:k_dnp_product_id_DS620];
    [set addIndex:k_dnp_product_id_DS820];
#endif
    
    return set.copy;
}
-(NSString *)stringForDSStatusString:(NSString *) status
{
    char * str = NULL;
    if([status isEqualToString:@"FU00001"])
    {
        str = "Firmware Update Mode";
    }
    else if (status)
    {
        switch (status.intValue)
        {
            default:
                str = "Unknown Status";
                break;
                // normal operation codes
            case k_dnp_printerStatus_idle:
                str = "Idle";
                break;
                
            case k_dnp_printerStatus_printing:
                str = "Printing";
                break;
            case k_dnp_printerStatus_headCooling:
                str = "Head cool down cycle";
                break;
            case k_dnp_printerStatus_motorCooling:
                str = "Motor cool down cycle";
                break;
            case k_dnp_printerStatus_standby:
                str = "Sleeping (standby mode)";
                break;
                
                // user-required action codes
            case k_dnp_printerStatus_coverOpen:
                str = "Cover Open";
                break;
            case k_dnp_printerStatus_scrapBoxMissing:
                str = "Scrap Box Missing";
                break;
            case k_dnp_printerStatus_paperEnd:
                str = "Paper End";
                break;
            case k_dnp_printerStatus_ribbonEnd:
                str = "Ribbon End";
                break;
            case k_dnp_printerStatus_paperJam:
                str = "Paper Jam";
                break;
            case k_dnp_printerStatus_ribbonErr:
                str = "Ribbon Error";
                break;
            case k_dnp_printerStatus_wrongPaper:
                str = "Wrong Paper";
                break;
            case k_dnp_printerStatus_badData:
                str = "Bad Data";
                break;
                
                // hardware codes
                // call for service if reboot does not cure
            case k_dnp_printerStatus_headVoltage:
                str = "Head Voltage Error";
                break;
            case k_dnp_printerStatus_headPositionErr:
                str = "Head Position Error";
                break;
            case k_dnp_printerStatus_powerSupply:
                str = "Power supply Error";
                break;
            case k_dnp_printerStatus_cutterError:
                str = "Cutter Error";
                break;
            case k_dnp_printerStatus_pinchRoller:
                str = "Pinch Roller Error";
                break;
            case k_dnp_printerStatus_headTempHigh:
                str = "Head Temp High Error";
                break;
            case k_dnp_printerStatus_mediaTemp:
                str = "Media Temp Error";
                break;
            case k_dnp_printerStatus_motorTemp:
                str = "Motor Temp Error";
                break;
            case k_dnp_printerStatus_ribbonTension:
                str = "Ribbon Tension Error";
                break;
            case k_dnp_printerStatus_rfidModule:
                str = "RFID Module Error";
                break;
            case k_dnp_printerStatus_systemFail:
                str = "System Fail Error";
                break;
        }
    }
    else
    {
        str = "<NULL>";
    }
    return str ? [NSString stringWithUTF8String:str]: @"";
}

-(NSString *)stringForCommandCode:(NSInteger)commandCode
{
    NSString * string = nil;
    NSString * cmd = nil;
    NSString * status = @"STATUS";
    NSString * info = @"INFO  ";   // arg 1 is always 6 char long
    NSString * cntrl = @"CNTRL ";
    NSString * image = @"IMAGE ";
    NSString * tableClear = @"TBL_CL";
    NSString * tableRead = @"TBL_RD";
    NSString * tableWrite = @"TBL_WT";
    NSString * maintRead = @"MNT_RD";
    NSString * maintWrite = @"MNT_WT";
    NSString * firmware = @"FW_UPD";

    switch (commandCode)
    {
            /// COLORTABLE COLORTABLE COLORTABLE COLORTABLE COLORTABLE COLORTABLE
        case DNP_COMMAND_SET_COLOR_TABLE_CLEAR:
            string = tableClear;
            break;
            
        case DNP_COMMAND_SET_COLOR_TABLE_DATA:
            string = tableWrite;
            cmd = @"CTRLD_UPDATE_CW";
            break;
            
        case DNP_COMMAND_SET_COLOR_TABLE_VERSION:
            string = tableWrite;
            cmd = @"Version";
            break;
            
        case DNP_COMMAND_GET_COLOR_TABLE_VERSION:
            string = tableRead;
            cmd = @"Version";
            break;
            
        case DNP_COMMAND_GET_COLOR_TABLE_VERSION_300:
            string = tableRead;
            cmd = @"CWD300_Version";
            break;
            
        case DNP_COMMAND_GET_COLOR_TABLE_VERSION_600:
            string = tableRead;
            cmd = @"CWD600_Version";
            break;
            
        case DNP_COMMAND_GET_COLOR_TABLE_VERSION_610:
            string = tableRead;
            cmd = @"CWD610_Version";
            break;
            
        case DNP_COMMAND_GET_COLOR_TABLE_VERSION_620:
            string = tableRead;
            cmd = @"CWD620_Version";
            break;
            
        case DNP_COMMAND_GET_COLOR_TABLE_CHECKSUM:
            string = maintRead;
            cmd = @"CTRLD_CHKSUM";
            break;
            
        case DNP_COMMAND_GET_COLOR_TABLE_CHECKSUM_300:
            string = tableRead;
            cmd = @"CWD300_Checksum";
            break;
            
        case DNP_COMMAND_GET_COLOR_TABLE_CHECKSUM_600:
            string = tableRead;
            cmd = @"CWD600_Checksum";
            break;
            
        case DNP_COMMAND_GET_COLOR_TABLE_CHECKSUM_610:
            string = tableRead;
            cmd = @"CWD610_Checksum";
            break;
            
        case DNP_COMMAND_GET_COLOR_TABLE_CHECKSUM_620:
            string = tableRead;
            cmd = @"CWD620_Checksum";
            break;
            
            /// MAINTENANCE MAINTENANCE MAINTENANCE MAINTENANCE MAINTENANCE MAINTENANCE
        case DNP_COMMAND_GET_COUNTER_LIFETIME:
            string = maintRead;
            cmd = @"COUNTER_LIFE";
            break;

        case DNP_COMMAND_GET_COUNTER_A:
            string = maintRead;
            cmd = @"COUNTER_A";
            break;
            
        case DNP_COMMAND_GET_COUNTER_B:
            string = maintRead;
            cmd = @"COUNTER_B";
            break;
            
        case DNP_COMMAND_GET_COUNTER_M:
            string = maintRead;
            cmd = @"COUNTER_M";
            break;
            
        case DNP_COMMAND_GET_COUNTER_MATTE:
            string = maintRead;
            cmd = @"COUNTER_MATTE";
            break;
            
        case DNP_COMMAND_GET_COUNTER_P:
            string = maintRead;
            cmd = @"COUNTER_P";
            break;
            
        case DNP_COMMAND_SET_COUNTER_P:
            string = maintWrite;
            cmd = @"COUNTERP_SET";
            break;
            
        case DNP_COMMAND_SET_COUNTER_ZERO:
            // Used for counters A,B, and M (set as payload arg 3)
            string = maintWrite;
            cmd = @"COUNTER_CLR";
            break;
            
        case DNP_COMMAND_GET_USB_iSerialNumber:
            string = maintRead;
            cmd = @"USB_ISERI_SET";
            break;
            
        case DNP_COMMAND_SET_USB_iSerialNumber:
            string = maintWrite;
            cmd = @"USB_ISERI_SET";
            break;
            
            /// FIRMWARE FIRMWARE FIRMWARE FIRMWARE FIRMWARE FIRMWARE
        case DNP_COMMAND_SET_MODE_FIRMWARE_WRITE:
            string = firmware;
            cmd = @"FLASH_REWRITE";
            break;
            
        case DNP_COMMAND_SET_FIRMWARE_UPLOAD:
            string = firmware;
            cmd = @"FLASH_PROGRAM";
            break;
            
            /// STATUS STATUS STATUS STATUS STATUS STATUS STATUS STATUS
        case DNP_COMMAND_GET_STATUS:
            string = status;
            break;
            
            /// INFO INFO INFO INFO INFO INFO INFO INFO INFO INFO INFO
        case DNP_COMMAND_GET_SERIAL_NUMBER:
            string = info;
            cmd = @"SERIAL_NUMBER";
            break;
            
        case DNP_COMMAND_GET_FREE_BUFFER_COUNT:
            string = info;
            cmd = @"FREE_PBUFFER";
            break;
            
        case DNP_COMMAND_GET_MEDIA_INFO:
            string = info;
            cmd = @"MEDIA";
            break;
            
        case DNP_COMMAND_GET_FIRMWARE_VERSION:
            string = info;
            cmd = @"FVER";
            break;
            
        case DNP_COMMAND_GET_PRINT_QTY_REMAINING:
            string = info;
            cmd = @"MQTY";
            break;
            
            // CNTRL CNTRL CNTRL CNTRL CNTRL CNTRL CNTRL CNTRL CNTRL
        case DNP_COMMAND_SET_OVERCOAT:
            string = cntrl;
            cmd = @"OVERCOAT";
            break;
            
        case DNP_COMMAND_SET_CUTTER:
            string = cntrl;
            cmd = @"CUTTER";
            break;
            
        case DNP_COMMAND_SET_PRINT_START:
            string = cntrl;
            cmd = @"START";
            break;
            
            // IMAGE IMAGE IMAGE IMAGE IMAGE IMAGE IMAGE IMAGE IMAGE IMAGE
        case DNP_COMMAND_SET_PAPERSIZE:
            string = image;
            cmd = @"MULTICUT";
            break;
            
        case DNP_COMMAND_SET_COLORPLANE_Y:
            string = image;
            cmd = @"YPLANE";
            break;
            
        case DNP_COMMAND_SET_COLORPLANE_M:
            string = image;
            cmd = @"MPLANE";
            break;
            
        case DNP_COMMAND_SET_COLORPLANE_C:
            string = image;
            cmd = @"CPLANE";
            break;
            
        default:
            break;
    }
    if(cmd)
    {
        string = [string stringByAppendingString:cmd];
    }
    //    DNP_COMMAND_GET_SENSOR_INFO,
    //    DNP_COMMAND_GET_RESOLUTION_H,
    //    DNP_COMMAND_GET_RESOLUTION_V,
    //    DNP_COMMAND_GET_MEDIA_LOT_OFFSET,
    //    DNP_COMMAND_GET_MEDIA_LOT_INFO,
    //
    //    DNP_COMMAND_SET_PRINT_RETRY,    // always set NO
    //
    //
    //    DNP_COMMAND_SET_COLOR_TABLE_CLEAR,  // Clear the color table before setting new data
    //    DNP_COMMAND_SET_COLOR_TABLE_DATA,   // transmit color control data from vendor provided file
    //    DNP_COMMAND_GET_COLOR_TABLE_VERSION,
    //    DNP_COMMAND_SET_COLOR_TABLE_VERSION,  // use filename (e.g. CX0100.CWD) and NULL pad to 4-byte boundary
    //    DNP_COMMAND_GET_COLOR_TABLE_CHECKSUM,
    //
    //    DNP_COMMAND_GET_COUNTER_LIFETIME,       // Lifetime counter (not resettable)
    //    DNP_COMMAND_GET_COUNTER_A,              // volume counter A (resettable)
    //    DNP_COMMAND_GET_COUNTER_B,              // volume counter B (resettable)
    //    DNP_COMMAND_SET_COUNTER_ZERO,           // use for both counter A and B and M (send which as arg 4)
    //    DNP_COMMAND_GET_COUNTER_P,              // volume counter P (resettable)
    //    DNP_COMMAND_SET_COUNTER_P,              // volume counter P (resettable)
    //    DNP_COMMAND_GET_COUNTER_MATTE,          // matte counter (not resettable)
    //    DNP_COMMAND_GET_COUNTER_M,              // matte counter (resettable)
    //
    //    DNP_COMMAND_SET_MODE_FIRMWARE_WRITE,    // set this mode before attempting firmware write
    //    DNP_COMMAND_SET_FIRMWARE_UPLOAD,        // write new firmware
    //    <ESC>P INFO FVER
    //    <ESC>P INFO SENSOR
    //    <ESC>P INFO MEDIA
    //    <ESC>P INFO RESOLUTION_H
    //    <ESC>P INFO RESOLUTION_V
    //    <ESC>P INFO MQTY
    //    <ESC>P INFO MCOLOR
    //    <ESC>P INFO MLOT
    return string;
}
-(void)errorWithString:(NSString *)errString code:(NSInteger)code
{
    // display an alert indicating the error described by string has occurred
//    NSError * e = [NSError errorWithDomain:@"InternalErrorDomain" code:code userInfo:[NSDictionary dictionaryWithObject:errString forKey:NSLocalizedDescriptionKey]];
//    [NSAlert alertWithError:e];
}
-(void)sendPrinterCommand:(DNP_CommandCode_t)commandCode withPayload:(NSData *)payloadData
{
    //each command is 32 bytes total beginning with <ESC>P
    // * some commands have additional data payload
    size_t cmdSize = 32;
    size_t payloadSize = 0;
    switch (commandCode)
    {
        case DNP_COMMAND_SET_PAPERSIZE:
        case DNP_COMMAND_SET_CUTTER:
        case DNP_COMMAND_SET_OVERCOAT:
        case DNP_COMMAND_SET_COUNTER_P:
            payloadSize = 8;
            break;

        case DNP_COMMAND_GET_COLOR_TABLE_VERSION_300:
        case DNP_COMMAND_GET_COLOR_TABLE_VERSION_600:
        case DNP_COMMAND_GET_COLOR_TABLE_VERSION_610:
        case DNP_COMMAND_GET_COLOR_TABLE_VERSION_620:
            
        case DNP_COMMAND_GET_COLOR_TABLE_CHECKSUM_300:
        case DNP_COMMAND_GET_COLOR_TABLE_CHECKSUM_600:
        case DNP_COMMAND_GET_COLOR_TABLE_CHECKSUM_610:
        case DNP_COMMAND_GET_COLOR_TABLE_CHECKSUM_620:
            
        case DNP_COMMAND_SET_COLORPLANE_Y: // extra is the size of the color plane data
        case DNP_COMMAND_SET_COLORPLANE_M: // extra is the size of the color plane data
        case DNP_COMMAND_SET_COLORPLANE_C: // extra is the size of the color plane data

        case DNP_COMMAND_SET_COLOR_TABLE_DATA:
        case DNP_COMMAND_SET_COLOR_TABLE_VERSION:
        case DNP_COMMAND_SET_FIRMWARE_UPLOAD:
            payloadSize = payloadData.length;
            break;
            
        case DNP_COMMAND_SET_COUNTER_ZERO: // payload of form 'C<n>0xD0x0' <n> = A,B,M
        case DNP_COMMAND_SET_USB_iSerialNumber: // payload of form '0<n>0xD0x0' <n> = 0 or 1
            payloadSize = 4;
            break;
            
        default:
            break;
    }
    cmdSize += payloadSize;
    char * command = malloc(cmdSize);
    //    unsigned long cpySize;
    //    size_t offset;
    
    if(NULL != command)
    {
        // initialize the command block with space chars
        // skip initing any payload block
        memset(command, 0x20 , MIN(32, cmdSize));
        
        command[0] = 0x1B;   // <ESC>
        command[1] = 'P';    // P
        NSString * cmdString = [self stringForCommandCode:commandCode];
        NSStringEncoding encoding = NSASCIIStringEncoding;
        if (! [cmdString getBytes:&command[2] maxLength:cmdSize usedLength:NULL encoding:encoding options:NO range:NSMakeRange(0, [cmdString lengthOfBytesUsingEncoding:encoding]) remainingRange:NULL])
        {
            [self errorWithString:[NSString stringWithFormat:NSLocalizedString(@"Internal error L%d",""),__LINE__] code:0];
        }
        
        //now copy any data payload size string (in ASCII base 10 digits!)
        if(0 < payloadSize)
        {
            if (! [[NSString stringWithFormat:@"%08zd", payloadSize] getBytes:&command[24] maxLength:8 usedLength:NULL encoding:encoding options:NO range:NSMakeRange(0,8) remainingRange:NULL])
            {
                [self errorWithString:[NSString stringWithFormat:NSLocalizedString(@"Internal error L%d",""),__LINE__] code:0];
            }
            
            // and add any payload data
            switch (commandCode)
            {
                case DNP_COMMAND_SET_PAPERSIZE:
                case DNP_COMMAND_SET_CUTTER:
                case DNP_COMMAND_SET_OVERCOAT:
                    if( 8 != payloadData.length)
                    {
                        break;
                    }
                    
                case DNP_COMMAND_SET_COLORPLANE_Y:
                case DNP_COMMAND_SET_COLORPLANE_M:
                case DNP_COMMAND_SET_COLORPLANE_C:
                    
                default:
                    memcpy(command + 32, payloadData.bytes, payloadData.length);
                    break;
            }
            
        }
        db("sending printer command %@ with payload size %u",cmdString,(unsigned)payloadSize);
        LOG_BYTES_LENGTH(command, MIN(64, cmdSize));
        
        NSString * errString = nil;
        // open the device if needed
        NSInteger thenState = self.state;
//        if(kUSBInterfaceIsOpen == thenState || (kIOReturnSuccess == [self openInterface]))
        {
            if(0 && 256 < cmdSize)
            {
                // split up the command for this dumb device
                if([self writeToInterfaceWithData:[NSData dataWithBytes:command length:cmdSize -256]])
                {
                    // now write the last 256 bytes
                    if(![self writeToInterfaceWithData:[NSData dataWithBytes:(command + cmdSize -256) length:256]])
                    {
                        //write error in _writeStatus
                        errString = [NSString stringWithFormat:NSLocalizedString(@"Communication error","Communication error")];
                    }
                }
                else
                {
                    //write error in _writeStatus
                    errString = [NSString stringWithFormat:NSLocalizedString(@"Communication error","Communication error")];
                }
            }
            else if(![self writeToInterfaceWithData:[NSData dataWithBytes:command length:cmdSize]])
            {
                NSLog(@"ERROR: writeToInterfaceWithData: [%x] (%s)",_writeStatus,strForIOReturn(_writeStatus));
                    //write error in _writeStatus
                    errString = [NSString stringWithFormat:NSLocalizedString(@"Communication error","Communication error")];
            }
            if(kUSBInterfaceIsOpen != thenState)
            {
                // close the interface if needed
//                [self closeInterface];
            }
        }
        free(command);

        if(errString)
        {
            [self errorWithString:errString code:0];
        }
    }
}
#pragma mark printer commands
-(void)resetCounter:(char)counterName
{
    [self openInterface:^(kern_return_t kr) {
        if(kIOReturnSuccess == kr) {
            switch (counterName)
            {
                case 'A':
                case 'B':
                case 'M':
                {
                    char counterZero[4] = {'C',counterName, 0x0d, '\0'};
                    NSData * payload = [NSData dataWithBytes:counterZero length:4];
                    [self sendPrinterCommand:DNP_COMMAND_SET_COUNTER_ZERO withPayload:payload];
                    
                    break;
                }
                default:
                    break;
            }
        }
    }];
}
-(void)resetCounterA
{
    [self resetCounter:'A'];
}
-(void)resetCounterB
{
    [self resetCounter:'B'];
}
-(void)resetCounterM
{
    [self resetCounter:'M'];
}
-(nullable NSString *)inlineGetFirmwareString {
    [self sendPrinterCommand:DNP_COMMAND_GET_FIRMWARE_VERSION withPayload:nil];
    NSData * reply = self.printerReply;
    const char * bytes = reply.bytes;
    // ex. reply "DS-RX1 01.10   "
    NSString * replyString = nil;
    if(reply.length > 0)
    {
        replyString = [NSString stringWithUTF8String:bytes];
        replyString = [replyString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
    }
    return replyString;
}
-(void)getPrinterFirmwareVersion
{
    [self openInterface:^(kern_return_t kr) {
        if(kIOReturnSuccess == kr) {
            [self sendPrinterCommand:^{
                [self sendPrinterCommand:DNP_COMMAND_GET_FIRMWARE_VERSION withPayload:nil];
            } replyQueue:nil reply:^(NSData *reply) {
                const char * bytes = reply.bytes;
                // ex. reply "DS-RX1 01.10   "
                if(reply.length > 0)
                {
                    NSString * replyString = [NSString stringWithUTF8String:bytes];
                    replyString = [replyString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
                    // we are on the main queue
                    [self willChangeValueForKey:ATMPPR_TBPrinterFirmwareVersion];
                    self.deviceInfoDict[ATMPPR_TBPrinterFirmwareVersion] = replyString;
                    [self didChangeValueForKey:ATMPPR_TBPrinterFirmwareVersion];
                    db("printer firmware version: %@",replyString);
                }
            }];
        }
    }];
}
-(void)updatePrinterSerialNumber
{
    [self openInterface:^(kern_return_t kr) {
        if(kIOReturnSuccess == kr) {
            [self sendPrinterCommand:^{
                [self sendPrinterCommand:DNP_COMMAND_GET_SERIAL_NUMBER withPayload:nil];
            } replyQueue:nil reply:^(NSData *reply) {
                NSString * key = ATMPPR_TBPrinterSerialNumber;
                // get reply
                const char * bytes = reply.bytes;
                // ex. reply "DS4Y11018757"
                NSString * replyString = reply.length ? [NSString stringWithUTF8String:bytes] : @"NULL";
                db("%@: %@",key, replyString);
                // we are on the main queue
                [self willChangeValueForKey:key];
                [self.deviceInfoDict setObject:replyString forKey:key];
                [self didChangeValueForKey:key];
            }];
        }
    }];
}
//-(void)convertWindowsUSBTraceDumpCSV {
    /*
    BEGIN {
        m=split(" !\"#$%&'\''()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~",a,"");
        for(i=1;i<=m;i++) {
            iToA[i+31]=a[i]};
        m=split("NUL SOH STX ETX EOT ENQ ACK BEL BS HT LF VT FF CR SO SI DLE DC1 DC2 DC3 DC4 NAK SYN ETB CAN EM SUB ESC FS GS RS US",b," ");
        for(i=1;i<=m;i++) {
            iToA[i-1]="<" b[i] ">"
        };
        iToA[127]="<DEL>";
    }
    function decimalToAscii(intVal) {
        return iToA[intVal]
    };
    function hexToAscii(hex,dec) {
        sub(/^0?[xX]/,"",hex);dec="0x" hex;return iToA[int(dec)]
    };
    /^[^ ]+Device[^ ]+,1B/ {
        t="";
        s=$0;
        sub(/^[^ ]+,1B/,"1B");
        max=NF;
        if(64<max){
            max=64
        };
        for (i=0;++i<max;) {
            t= t hexToAscii($i)
        };
        print t;
    };
    /NSFER,IN,/ {
        if (1 < NF) {
            n=split($0,a,",");
            s=a[n-1];
            m=split(s,b," ");
            t="";
            for (i=0;++i<=m;) {
                t= t hexToAscii(b[i])};
            print t,"(" s ")";
        }
    }
    */
//}
-(void)updatePrinterStatus{
    [self getPrinterStatus:nil];
//    [self getPrinterStatus:^(NSString *statusString) {
//        // we are already on the main_queue
//        db("printer status code: %@",statusString);
//        statusString = [self stringForDSStatusString:statusString];
//        self.printerStatus = statusString;
//        db("printer status string: %@",statusString);
//    }];
}
-(void)getPrinterStatus:(void (^)(NSString* statusString))replyBlock {
    [self openInterface:^(kern_return_t kr) {
        if(kIOReturnSuccess == kr) {
            [self sendPrinterCommand:^{
                [self sendPrinterCommand:DNP_COMMAND_GET_STATUS withPayload:nil];
            } replyQueue:nil reply:^(NSData *replyData) {
                if(replyData.length) {
                    NSString * replyString = [NSString stringWithUTF8String:replyData.bytes];
                    NSString * statusString = [self stringForDSStatusString:replyString];
                    self.printerStatus = statusString;
                    if(replyBlock) {
                        replyBlock(replyString);
                    }
                } else {
                    // bad reply from printer ... sleep a bit and try again
                    NSLog(@"Received a null printer status reply ... will try again later");
                    usleep(250000);
                    [self getPrinterStatus:replyBlock];
                }
            }];
        }
    }];
}
-(void)getPrinterColorTableVersion:(void (^)(NSArray<NSString*>* array))replyBlock {
    
    // this method varies depending on model. older models have only one version to get (though 2 to set)
    // 620 and newer models have 3 or more to get (and set)
    NSUInteger count = 0;
    NSMutableArray* m_array = NSMutableArray.array;
    NSData* payload = nil;
    switch (_idProduct)
    {
        case k_dnp_product_id_DS40:
        case k_dnp_product_id_DS80:
        case k_dnp_product_id_DS80D:
        case k_dnp_product_id_RX1:
        {
//            files = @[@"DS40_0140.fw", @"DS40_300_0130.cwd", @"DS40_600_0130.cwd"];
            [self sendPrinterCommand:^{
                [self sendPrinterCommand:DNP_COMMAND_GET_COLOR_TABLE_VERSION withPayload:nil];
            } replyQueue:nil reply:^(NSData *replyData) {
                NSString * replyString = replyData.length ? [NSString stringWithUTF8String:replyData.bytes] : @"NULL";
                replyString = [replyString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
                [self willChangeValueForKey:ATMPPR_TBPrinterColorTableVersion];
                [self.deviceInfoDict setObject:replyString forKey:ATMPPR_TBPrinterColorTableVersion];
                [self didChangeValueForKey:ATMPPR_TBPrinterColorTableVersion];
                db("printer color table version: %@",replyString);
                if(replyBlock) {
                    replyBlock(@[replyString]);
                }
            }];
            break;
        }
        case k_dnp_product_id_DS820:
        {
            count = 8;
            // the 680 has 8 strings to get 4 for each of 2 media types
            payload = self.commandPayloadForMediaSD;
            [self sendPrinterCommand:^{
                [self sendPrinterCommand:DNP_COMMAND_GET_COLOR_TABLE_VERSION_620 withPayload:payload];
            } replyQueue:nil reply:^(NSData *replyData) {
                NSString * replyString = replyData.length ? [NSString stringWithUTF8String:replyData.bytes] : @"NULL";
                replyString = [replyString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
                [self willChangeValueForKey:ATMPPR_TBPrinterColorTableVersion620];
                [self.deviceInfoDict setObject:replyString forKey:ATMPPR_TBPrinterColorTableVersion620];
                [self didChangeValueForKey:ATMPPR_TBPrinterColorTableVersion620];
                db("printer color table version 620: %@",replyString);
                [m_array addObject:replyString];
                if(m_array.count == count && replyBlock) {
                    replyBlock(m_array);
                }
            }];
            [self sendPrinterCommand:^{
                [self sendPrinterCommand:DNP_COMMAND_GET_COLOR_TABLE_VERSION_610 withPayload:payload];
            } replyQueue:nil reply:^(NSData *replyData) {
                NSString * replyString = replyData.length ? [NSString stringWithUTF8String:replyData.bytes] : @"NULL";
                replyString = [replyString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
                [self willChangeValueForKey:ATMPPR_TBPrinterColorTableVersion610];
                [self.deviceInfoDict setObject:replyString forKey:ATMPPR_TBPrinterColorTableVersion610];
                [self didChangeValueForKey:ATMPPR_TBPrinterColorTableVersion610];
                db("printer color table version 610: %@",replyString);
                [m_array addObject:replyString];
                if(m_array.count == count && replyBlock) {
                    replyBlock(m_array);
                }
            }];
            [self sendPrinterCommand:^{
                [self sendPrinterCommand:DNP_COMMAND_GET_COLOR_TABLE_VERSION_600 withPayload:payload];
            } replyQueue:nil reply:^(NSData *replyData) {
                NSString * replyString = replyData.length ? [NSString stringWithUTF8String:replyData.bytes] : @"NULL";
                replyString = [replyString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
                [self willChangeValueForKey:ATMPPR_TBPrinterColorTableVersion600];
                [self.deviceInfoDict setObject:replyString forKey:ATMPPR_TBPrinterColorTableVersion600];
                [self didChangeValueForKey:ATMPPR_TBPrinterColorTableVersion600];
                db("printer color table version 600: %@",replyString);
                [m_array addObject:replyString];
                if(m_array.count == count && replyBlock) {
                    replyBlock(m_array);
                }
            }];
            [self sendPrinterCommand:^{
                [self sendPrinterCommand:DNP_COMMAND_GET_COLOR_TABLE_VERSION_300 withPayload:payload];
            } replyQueue:nil reply:^(NSData *replyData) {
                NSString * replyString = replyData.length ? [NSString stringWithUTF8String:replyData.bytes] : @"NULL";
                replyString = [replyString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
                [self willChangeValueForKey:ATMPPR_TBPrinterColorTableVersion];
                [self.deviceInfoDict setObject:replyString forKey:ATMPPR_TBPrinterColorTableVersion];
                [self didChangeValueForKey:ATMPPR_TBPrinterColorTableVersion];
                db("printer color table version 300: %@",replyString);
                [m_array addObject:replyString];
                if(m_array.count == count && replyBlock) {
                    replyBlock(m_array);
                }
            }];
            payload = self.commandPayloadForMediaPP;
            [self sendPrinterCommand:^{
                [self sendPrinterCommand:DNP_COMMAND_GET_COLOR_TABLE_VERSION_620 withPayload:payload];
            } replyQueue:nil reply:^(NSData *replyData) {
                NSString * replyString = replyData.length ? [NSString stringWithUTF8String:replyData.bytes] : @"NULL";
                replyString = [replyString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
                [self willChangeValueForKey:ATMPPR_TBPrinterColorTableVersion620HD];
                [self.deviceInfoDict setObject:replyString forKey:ATMPPR_TBPrinterColorTableVersion620HD];
                [self didChangeValueForKey:ATMPPR_TBPrinterColorTableVersion620HD];
                db("printer color table version 620HD: %@",replyString);
                [m_array addObject:replyString];
                if(m_array.count == count && replyBlock) {
                    replyBlock(m_array);
                }
            }];
            [self sendPrinterCommand:^{
                [self sendPrinterCommand:DNP_COMMAND_GET_COLOR_TABLE_VERSION_610 withPayload:payload];
            } replyQueue:nil reply:^(NSData *replyData) {
                NSString * replyString = replyData.length ? [NSString stringWithUTF8String:replyData.bytes] : @"NULL";
                replyString = [replyString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
                [self willChangeValueForKey:ATMPPR_TBPrinterColorTableVersion610HD];
                [self.deviceInfoDict setObject:replyString forKey:ATMPPR_TBPrinterColorTableVersion610HD];
                [self didChangeValueForKey:ATMPPR_TBPrinterColorTableVersion610HD];
                db("printer color table version 610HD: %@",replyString);
                [m_array addObject:replyString];
                if(m_array.count == count && replyBlock) {
                    replyBlock(m_array);
                }
            }];
            [self sendPrinterCommand:^{
                [self sendPrinterCommand:DNP_COMMAND_GET_COLOR_TABLE_VERSION_600 withPayload:payload];
            } replyQueue:nil reply:^(NSData *replyData) {
                NSString * replyString = replyData.length ? [NSString stringWithUTF8String:replyData.bytes] : @"NULL";
                replyString = [replyString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
                [self willChangeValueForKey:ATMPPR_TBPrinterColorTableVersion600HD];
                [self.deviceInfoDict setObject:replyString forKey:ATMPPR_TBPrinterColorTableVersion600HD];
                [self didChangeValueForKey:ATMPPR_TBPrinterColorTableVersion600HD];
                db("printer color table version 600HD: %@",replyString);
                [m_array addObject:replyString];
                if(m_array.count == count && replyBlock) {
                    replyBlock(m_array);
                }
            }];
            [self sendPrinterCommand:^{
                [self sendPrinterCommand:DNP_COMMAND_GET_COLOR_TABLE_VERSION_300 withPayload:payload];
            } replyQueue:nil reply:^(NSData *replyData) {
                NSString * replyString = replyData.length ? [NSString stringWithUTF8String:replyData.bytes] : @"NULL";
                replyString = [replyString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
                [self willChangeValueForKey:ATMPPR_TBPrinterColorTableVersion300HD];
                [self.deviceInfoDict setObject:replyString forKey:ATMPPR_TBPrinterColorTableVersion300HD];
                [self didChangeValueForKey:ATMPPR_TBPrinterColorTableVersion300HD];
                db("printer color table version 300HD: %@",replyString);
                [m_array addObject:replyString];
                if(m_array.count == count && replyBlock) {
                    replyBlock(m_array);
                }
            }];
            
            break;
        }
        case k_dnp_product_id_DS620:
        {
            if(!count) {
                count = 3;
            }
            // the 620 has 3 strings to get
            [self sendPrinterCommand:^{
                [self sendPrinterCommand:DNP_COMMAND_GET_COLOR_TABLE_VERSION_610 withPayload:nil];
            } replyQueue:nil reply:^(NSData *replyData) {
                NSString * replyString = replyData.length ? [NSString stringWithUTF8String:replyData.bytes] : @"NULL";
                replyString = [replyString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
                [self willChangeValueForKey:ATMPPR_TBPrinterColorTableVersion610];
                [self.deviceInfoDict setObject:replyString forKey:ATMPPR_TBPrinterColorTableVersion610];
                [self didChangeValueForKey:ATMPPR_TBPrinterColorTableVersion610];
                db("printer color table version 610: %@",replyString);
                [m_array addObject:replyString];
                if(m_array.count == count && replyBlock) {
                    replyBlock(m_array);
                }
            }];
            [self sendPrinterCommand:^{
                [self sendPrinterCommand:DNP_COMMAND_GET_COLOR_TABLE_VERSION_600 withPayload:nil];
            } replyQueue:nil reply:^(NSData *replyData) {
                NSString * replyString = replyData.length ? [NSString stringWithUTF8String:replyData.bytes] : @"NULL";
                replyString = [replyString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
                [self willChangeValueForKey:ATMPPR_TBPrinterColorTableVersion600];
                [self.deviceInfoDict setObject:replyString forKey:ATMPPR_TBPrinterColorTableVersion600];
                [self didChangeValueForKey:ATMPPR_TBPrinterColorTableVersion600];
                db("printer color table version 600: %@",replyString);
                [m_array addObject:replyString];
                if(m_array.count == count && replyBlock) {
                    replyBlock(m_array);
                }
            }];
            [self sendPrinterCommand:^{
                [self sendPrinterCommand:DNP_COMMAND_GET_COLOR_TABLE_VERSION_300 withPayload:nil];
            } replyQueue:nil reply:^(NSData *replyData) {
                NSString * replyString = replyData.length ? [NSString stringWithUTF8String:replyData.bytes] : @"NULL";
                replyString = [replyString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
                [self willChangeValueForKey:ATMPPR_TBPrinterColorTableVersion];
                [self.deviceInfoDict setObject:replyString forKey:ATMPPR_TBPrinterColorTableVersion];
                [self didChangeValueForKey:ATMPPR_TBPrinterColorTableVersion];
                db("printer color table version 300: %@",replyString);
                [m_array addObject:replyString];
                if(m_array.count == count && replyBlock) {
                    replyBlock(m_array);
                }
            }];
            
            break;
        }
        default:
            break;
    }
}
-(void)getPrinterColorTableChecksum:(void (^)(NSArray<NSString*>* array))replyBlock {
    
    // this method varies depending on model. older models have only one version to get (though 2 to set)
    // 620 and newer models have 3 or more to get (and set), 820 has 8
    NSUInteger count = 0;
    NSMutableArray* m_array = NSMutableArray.array;
    NSData* payload = nil;
    switch (_idProduct)
    {
        case k_dnp_product_id_DS40:
        case k_dnp_product_id_DS80:
        case k_dnp_product_id_DS80D:
        case k_dnp_product_id_RX1:
        {
            [self sendPrinterCommand:^{
                [self sendPrinterCommand:DNP_COMMAND_GET_COLOR_TABLE_CHECKSUM withPayload:nil];
            } replyQueue:nil reply:^(NSData *replyData) {
                NSString * replyString = replyData.length ? [NSString stringWithUTF8String:replyData.bytes] : @"NULL";
                replyString = [replyString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
                [self willChangeValueForKey:ATMPPR_TBPrinterColorTableChecksum300];
                [self.deviceInfoDict setObject:replyString forKey:ATMPPR_TBPrinterColorTableChecksum300];
                [self didChangeValueForKey:ATMPPR_TBPrinterColorTableChecksum300];
                db("printer color table checksum: %@",replyString);
                if(replyBlock) {
                    replyBlock(@[replyString]);
                }
            }];
            break;
        }
        case k_dnp_product_id_DS820:
        {
            count = 8;
            // the 680 has 8 strings to get 4 for each of 2 media types
            payload = self.commandPayloadForMediaSD;
            [self sendPrinterCommand:^{
                [self sendPrinterCommand:DNP_COMMAND_GET_COLOR_TABLE_CHECKSUM_620 withPayload:payload];
            } replyQueue:nil reply:^(NSData *replyData) {
                NSString * replyString = replyData.length ? [NSString stringWithUTF8String:replyData.bytes] : @"NULL";
                replyString = [replyString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
                [self willChangeValueForKey:ATMPPR_TBPrinterColorTableChecksum620];
                [self.deviceInfoDict setObject:replyString forKey:ATMPPR_TBPrinterColorTableChecksum620];
                [self didChangeValueForKey:ATMPPR_TBPrinterColorTableChecksum620];
                db("printer color table CHECKSUM 620: %@",replyString);
                [m_array addObject:replyString];
                if(m_array.count == count && replyBlock) {
                    replyBlock(m_array);
                }
            }];
            [self sendPrinterCommand:^{
                [self sendPrinterCommand:DNP_COMMAND_GET_COLOR_TABLE_CHECKSUM_610 withPayload:payload];
            } replyQueue:nil reply:^(NSData *replyData) {
                NSString * replyString = replyData.length ? [NSString stringWithUTF8String:replyData.bytes] : @"NULL";
                replyString = [replyString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
                [self willChangeValueForKey:ATMPPR_TBPrinterColorTableChecksum610];
                [self.deviceInfoDict setObject:replyString forKey:ATMPPR_TBPrinterColorTableChecksum610];
                [self didChangeValueForKey:ATMPPR_TBPrinterColorTableChecksum610];
                db("printer color table CHECKSUM 610: %@",replyString);
                [m_array addObject:replyString];
                if(m_array.count == count && replyBlock) {
                    replyBlock(m_array);
                }
            }];
            [self sendPrinterCommand:^{
                [self sendPrinterCommand:DNP_COMMAND_GET_COLOR_TABLE_CHECKSUM_600 withPayload:payload];
            } replyQueue:nil reply:^(NSData *replyData) {
                NSString * replyString = replyData.length ? [NSString stringWithUTF8String:replyData.bytes] : @"NULL";
                replyString = [replyString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
                [self willChangeValueForKey:ATMPPR_TBPrinterColorTableChecksum600];
                [self.deviceInfoDict setObject:replyString forKey:ATMPPR_TBPrinterColorTableChecksum600];
                [self didChangeValueForKey:ATMPPR_TBPrinterColorTableChecksum600];
                db("printer color table CHECKSUM 600: %@",replyString);
                [m_array addObject:replyString];
                if(m_array.count == count && replyBlock) {
                    replyBlock(m_array);
                }
            }];
            [self sendPrinterCommand:^{
                [self sendPrinterCommand:DNP_COMMAND_GET_COLOR_TABLE_CHECKSUM_300 withPayload:payload];
            } replyQueue:nil reply:^(NSData *replyData) {
                NSString * replyString = replyData.length ? [NSString stringWithUTF8String:replyData.bytes] : @"NULL";
                replyString = [replyString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
                [self willChangeValueForKey:ATMPPR_TBPrinterColorTableChecksum300];
                [self.deviceInfoDict setObject:replyString forKey:ATMPPR_TBPrinterColorTableChecksum300];
                [self didChangeValueForKey:ATMPPR_TBPrinterColorTableChecksum300];
                db("printer color table CHECKSUM 300: %@",replyString);
                [m_array addObject:replyString];
                if(m_array.count == count && replyBlock) {
                    replyBlock(m_array);
                }
            }];
            payload = self.commandPayloadForMediaPP;
            [self sendPrinterCommand:^{
                [self sendPrinterCommand:DNP_COMMAND_GET_COLOR_TABLE_CHECKSUM_620 withPayload:payload];
            } replyQueue:nil reply:^(NSData *replyData) {
                NSString * replyString = replyData.length ? [NSString stringWithUTF8String:replyData.bytes] : @"NULL";
                replyString = [replyString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
                [self willChangeValueForKey:ATMPPR_TBPrinterColorTableChecksum620HD];
                [self.deviceInfoDict setObject:replyString forKey:ATMPPR_TBPrinterColorTableChecksum620HD];
                [self didChangeValueForKey:ATMPPR_TBPrinterColorTableChecksum620HD];
                db("printer color table CHECKSUM 620HD: %@",replyString);
                [m_array addObject:replyString];
                if(m_array.count == count && replyBlock) {
                    replyBlock(m_array);
                }
            }];
            [self sendPrinterCommand:^{
                [self sendPrinterCommand:DNP_COMMAND_GET_COLOR_TABLE_CHECKSUM_610 withPayload:payload];
            } replyQueue:nil reply:^(NSData *replyData) {
                NSString * replyString = replyData.length ? [NSString stringWithUTF8String:replyData.bytes] : @"NULL";
                replyString = [replyString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
                [self willChangeValueForKey:ATMPPR_TBPrinterColorTableChecksum610HD];
                [self.deviceInfoDict setObject:replyString forKey:ATMPPR_TBPrinterColorTableChecksum610HD];
                [self didChangeValueForKey:ATMPPR_TBPrinterColorTableChecksum610HD];
                db("printer color table CHECKSUM 610HD: %@",replyString);
                [m_array addObject:replyString];
                if(m_array.count == count && replyBlock) {
                    replyBlock(m_array);
                }
            }];
            [self sendPrinterCommand:^{
                [self sendPrinterCommand:DNP_COMMAND_GET_COLOR_TABLE_CHECKSUM_600 withPayload:payload];
            } replyQueue:nil reply:^(NSData *replyData) {
                NSString * replyString = replyData.length ? [NSString stringWithUTF8String:replyData.bytes] : @"NULL";
                replyString = [replyString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
                [self willChangeValueForKey:ATMPPR_TBPrinterColorTableChecksum600HD];
                [self.deviceInfoDict setObject:replyString forKey:ATMPPR_TBPrinterColorTableChecksum600HD];
                [self didChangeValueForKey:ATMPPR_TBPrinterColorTableChecksum600HD];
                db("printer color table CHECKSUM 600HD: %@",replyString);
                [m_array addObject:replyString];
                if(m_array.count == count && replyBlock) {
                    replyBlock(m_array);
                }
            }];
            [self sendPrinterCommand:^{
                [self sendPrinterCommand:DNP_COMMAND_GET_COLOR_TABLE_CHECKSUM_300 withPayload:payload];
            } replyQueue:nil reply:^(NSData *replyData) {
                NSString * replyString = replyData.length ? [NSString stringWithUTF8String:replyData.bytes] : @"NULL";
                replyString = [replyString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
                [self willChangeValueForKey:ATMPPR_TBPrinterColorTableChecksum300HD];
                [self.deviceInfoDict setObject:replyString forKey:ATMPPR_TBPrinterColorTableChecksum300HD];
                [self didChangeValueForKey:ATMPPR_TBPrinterColorTableChecksum300HD];
                db("printer color table CHECKSUM 300HD: %@",replyString);
                [m_array addObject:replyString];
                if(m_array.count == count && replyBlock) {
                    replyBlock(m_array);
                }
            }];
            
            break;
        }
        case k_dnp_product_id_DS620:
        {
            if(!count) {
                count = 3;
            }
            // the 620 has 3 strings to get
            [self sendPrinterCommand:^{
                [self sendPrinterCommand:DNP_COMMAND_GET_COLOR_TABLE_CHECKSUM_610 withPayload:nil];
            } replyQueue:nil reply:^(NSData *replyData) {
                NSString * replyString = replyData.length ? [NSString stringWithUTF8String:replyData.bytes] : @"NULL";
                replyString = [replyString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
                [self willChangeValueForKey:ATMPPR_TBPrinterColorTableChecksum610];
                [self.deviceInfoDict setObject:replyString forKey:ATMPPR_TBPrinterColorTableChecksum610];
                [self didChangeValueForKey:ATMPPR_TBPrinterColorTableChecksum610];
                db("printer color table CHECKSUM 610: %@",replyString);
                [m_array addObject:replyString];
                if(m_array.count == count && replyBlock) {
                    replyBlock(m_array);
                }
            }];
            [self sendPrinterCommand:^{
                [self sendPrinterCommand:DNP_COMMAND_GET_COLOR_TABLE_CHECKSUM_600 withPayload:nil];
            } replyQueue:nil reply:^(NSData *replyData) {
                NSString * replyString = replyData.length ? [NSString stringWithUTF8String:replyData.bytes] : @"NULL";
                replyString = [replyString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
                [self willChangeValueForKey:ATMPPR_TBPrinterColorTableChecksum600];
                [self.deviceInfoDict setObject:replyString forKey:ATMPPR_TBPrinterColorTableChecksum600];
                [self didChangeValueForKey:ATMPPR_TBPrinterColorTableChecksum600];
                db("printer color table CHECKSUM 600: %@",replyString);
                [m_array addObject:replyString];
                if(m_array.count == count && replyBlock) {
                    replyBlock(m_array);
                }
            }];
            [self sendPrinterCommand:^{
                [self sendPrinterCommand:DNP_COMMAND_GET_COLOR_TABLE_CHECKSUM_300 withPayload:nil];
            } replyQueue:nil reply:^(NSData *replyData) {
                NSString * replyString = replyData.length ? [NSString stringWithUTF8String:replyData.bytes] : @"NULL";
                replyString = [replyString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
                [self willChangeValueForKey:ATMPPR_TBPrinterColorTableChecksum300];
                [self.deviceInfoDict setObject:replyString forKey:ATMPPR_TBPrinterColorTableChecksum300];
                [self didChangeValueForKey:ATMPPR_TBPrinterColorTableChecksum300];
                db("printer color table version 300: %@",replyString);
                [m_array addObject:replyString];
                if(m_array.count == count && replyBlock) {
                    replyBlock(m_array);
                }
            }];
            
            break;
        }
        default:
            break;
    }
}
-(void)getPrinterLifetimeCounter
{
    [self openInterface:^(kern_return_t kr) {
        if(kIOReturnSuccess == kr) {
            [self sendPrinterCommand:^{
                [self sendPrinterCommand:DNP_COMMAND_GET_COUNTER_LIFETIME withPayload:nil];
            } replyQueue:nil reply:^(NSData *reply) {
                const char * bytes = reply.bytes;
                // ex. reply "CL0000102"
                if(reply.length > 0)
                {
                    NSString * replyString = [NSString stringWithUTF8String:bytes];
                    if([replyString hasPrefix:@"CL"])
                    {
                        replyString = [replyString substringFromIndex:2];
                        // one way to strip leading zeros ...
                        replyString = [[NSNumber numberWithInt:replyString.intValue] stringValue];
                    }
                    // we are on the main queue
                    [self willChangeValueForKey:@"lifetimeCounter"];
                    [self.deviceInfoDict setObject:replyString forKey:ATMPPR_TBPrinterLifetimeCounter];
                    [self didChangeValueForKey:@"lifetimeCounter"];
                    db("lifetime counter value: %@",replyString);
                }
            }];
        }
    }];
}
-(BOOL)hasISerialSupport {
    BOOL hasSupport = NO;

    switch (_idProduct)
    {
        case k_dnp_product_id_DS40:
        case k_dnp_product_id_DS80:
        case k_dnp_product_id_DS80D:
            break;
        case k_dnp_product_id_DS820:
        case k_dnp_product_id_DS620:
            hasSupport = YES;
            break;
        case k_dnp_product_id_RX1:
            if(self.installedFirmwareVersion >= 2.04) {
                hasSupport = YES;
            }
            break;
    }
return hasSupport;
}
-(void)getUSB_ISerial
{
    // we do not want to send this command to printers that do not support it as it causes a stall and subsequent errors
    // 620 and 820 have support (all firmware versions
    // RX-1 with firmware 2.04 and higher has support
    // all others do not (currently)
    
    if(self.hasISerialSupport) {
        [self openInterface:^(kern_return_t kr) {
            if(kIOReturnSuccess == kr) {
                [self sendPrinterCommand:^{
                    [self sendPrinterCommand:DNP_COMMAND_GET_USB_iSerialNumber withPayload:nil];
                } replyQueue:nil reply:^(NSData *reply) {
                    const char * bytes = reply.bytes;
                    // ex. reply "01\r\0"
                    //response is a string starting 0nnnn\r
                    if(reply.length > 0)
                    {
                        NSString * replyString = [NSString stringWithUTF8String:bytes];
                        if([replyString length])
                        {
                            // one way to strip leading zeros ...
                            replyString = [[NSNumber numberWithInt:replyString.intValue] stringValue];
                        }
                        // we are on main queue
                        [self willChangeValueForKey:@"usbISerialString"];
                        self.deviceInfoDict[ATMPPR_DNP_USB_ISerialisSet] = replyString;
                        [self didChangeValueForKey:@"usbISerialString"];
                        self.usbISerial = replyString.boolValue;
                        db("usbISerial value: %@",replyString);
                    }
                }];
                [self closeInterface:nil];
            }
        }];
    }
}
-(NSData*)commandPayloadForMediaSD {
    char message[4] = {'0','0','0', '1'};
    NSData * payload = [NSData dataWithBytes:message length:4];
    return payload;
}
-(NSData*)commandPayloadForMediaPP {
    char message[4] = {'0','0','0', '3'};
    NSData * payload = [NSData dataWithBytes:message length:4];
    return payload;
}
-(void)setUSB_ISerial:(BOOL)enable
{
    if(self.hasISerialSupport) {
        [self openInterface:^(kern_return_t kr) {
            if(kIOReturnSuccess == kr) {
                char message[4] = {'0',enable ? '1' : '0', 0x0d, '\0'};
                NSData * payload = [NSData dataWithBytes:message length:4];
                [self sendPrinterCommand:DNP_COMMAND_SET_USB_iSerialNumber withPayload:payload];
                [self getUSB_ISerial];
            }
        }];
    }
}
-(void)getDeviceCustomInfo
{
    //subclasses call into the device to get properties (e.g. firmware version, counter value, etc... and fill the device properties dictionary
    
    // perform a series of transactions with the device to get the available device info
    
    // check for firmware update mode FU00001
    [self getPrinterStatus:^(NSString *statusString) {
        if (![statusString isEqualToString:@"FU00001"]) {
            // only perform if device is *not* in firmware update mode
            [self openInterface:^(kern_return_t kr) {
                if(kIOReturnSuccess == kr) {
//                    [self updatePrinterStatus];
                    [self getPrinterFirmwareVersion];
                    [self getPrinterLifetimeCounter];
                    [self getPrinterColorTableVersion:nil];
                    [self getPrinterColorTableChecksum:nil];
                    [self updatePrinterSerialNumber]; // 2013-02-27steve
                    [self getUSB_ISerial];
                    
                    [self closeInterface:nil];
                }
            }];

            // 2013-02-19steve - tyler - not sure where best - but gets the new version
//            [self verifyFirmwareArchive]; // 2013-02-19steve
//            NSLog(@"newFirmwareVersion  %@", self.newFirmwareVersion);// 2013-02-19steve
        }
    }];
}
#pragma mark utility/helper methods
-(NSString*)asciiHexForData:(NSData*)data {
    NSMutableString *dump = [NSMutableString string];
    const UInt8 * d = (const UInt8*)data.bytes;
    unsigned long i;
    NSUInteger length = data.length;
    for (i=0; i < length; i++) {
        [dump appendFormat:@"%.2x",d[i]];
    }
    return dump;
}
-(NSString *)verifyBlob:(NSData *)data {
    NSString * classy = nil;
    size_t length = CC_SHA256_DIGEST_LENGTH;
    if(data.length)
    {
        classy = NSStringFromClass(self.class);
        NSMutableData * m_data = [NSMutableData dataWithLength:length+1];
        char * hBytes = m_data.mutableBytes;
        if(hBytes)
        {
            db("keyed on %@",classy);

            CCHmac(kCCHmacAlgSHA256, classy.UTF8String, classy.length, data.bytes, data.length, hBytes);
            hBytes[length] = '\0';
            classy = [self asciiHexForData:m_data];
//            db("computed hash data:");
//            LOG_DATA(m_data);
        }
    }
    return classy;
}
-(BOOL)verifyFirmwareArchive {
    NSLog(@"%s",__func__);
    BOOL didVerify = NO;
    NSData * data;
    NSString * name;
    NSArray * files = nil;
    NSArray * hashes = nil;
    NSString * type = @"cwd";
    db("switching on productID 0x%x",_idProduct);
    switch (_idProduct) {
            // Important Important Important Important Important Important Important Important Important
            
            // all of the files listed below must be added to the appropriate target "copy file" build phase
            
            // Important Important Important Important Important Important Important Important Important
            
        case k_dnp_product_id_DS40:
            files = @[
                      @"DS40 01.61.fw",
                      @"DS40_300_0161.cwd",
                      @"DS40_600_0161.cwd",
                      ];
            hashes = @[
                       @"15c23bb0a6caa1a9ad89c3494a752bcd3db338aec18d7b2b88b24b628acc83cf00",
                       @"1805d831e72423827b70ebb4aff26d5db87fc813be4a82685d1f8044ff075bc700",
                       @"c6c8aa494dd9c449f34c3c40515d994a009bc7f69cfba3cfb02a032a2226a65300",
                       ];
            break;
        case k_dnp_product_id_DS80:
            files = @[
                      @"DS80 01.30.fw",
                      @"DS80_300_0130.cwd",
                      @"DS80_600_0130.cwd",
                      ];
            hashes = @[
                       @"95a1f829050fee267b31f4fb86c8464f903560a5bc886f3e0ad28957e9058ca100",
                       @"2e4cc45ba435f83e128b7f30b725249eaff9e945169b0e23588625164b449bb600",
                       @"a9d730ed76c60c441bf5c75cc1cd9b037a30b066383ecbd90e806b198744e80700",
                       ];
            break;
        case k_dnp_product_id_DS80D:
            files = @[
                      @"DS80DUP 01.13.fw",
                      @"DS80_300_0130.cwd",
                      @"DS80_600_0130.cwd",
                      ];
            hashes = @[
                       @"4d8ec3f0e343c0dbffa74bb16ac585f05c8c564310b416bfee00470686761bb300",
                       @"2e4cc45ba435f83e128b7f30b725249eaff9e945169b0e23588625164b449bb600",
                       @"a9d730ed76c60c441bf5c75cc1cd9b037a30b066383ecbd90e806b198744e80700",
                       ];
            break;
        case k_dnp_product_id_RX1:
//            files = @[@"DS-RX1_0110.fw", @"DS-RX1_300_0100.cwd", @"DS-RX1_600_0100.cwd"]; // previous version files
            files = @[
                      @"DS-RX1 02.06.fw",
                      @"DS-RX1_300_0201.cwd",
                      @"DS-RX1_600_0100.cwd",
                      ];
            hashes = @[
                       @"ba2d26abeff83d5f91a9c0d356e81918147fec9519b69c47481dde6af657b28100",
                       @"e455310328cd94b7956e6c29604a04db505b31c00086b161e1ecd27f3858a2b200",
                       @"0ca5e7289b486217f83f2b81fe7183bb306fe3296241d4beccad4fd1030bc9c800",
                       ];
            break;
        case k_dnp_product_id_DS620:
            files = @[
                      @"DS620 01.52.fw",
                      @"DS620_300_0111.cwd",
                      @"DS620_600_0111.cwd",
                      @"DS620_610_0111.cwd",
                      ];
            hashes = @[
                       @"6ecbade52c1bde6769bb9854c7bfb8e602c9067a6b43beb2878663aed56f845200",
                       @"1283dfaf9a90f086739ec7f12283e6ffef0e45e2e3adeda8b7386cc5e0e2c12400",
                       @"a7f1655d6484ad30ee22edff604a534528abcf8edf785575b4aee22fda8fbbe100",
                       @"ead58a0ea2a607fdedef36be4a601b2b9309ef0430e4c456e5f2f9f929ddeb4200",
                       ];
            break;
        case k_dnp_product_id_DS820:
            files = @[
                      @"DS820 01.05.fw",
                      @"DS820_SD_300_0101.cwd",
                      @"DS820_PP_300_0101.cwd",
                      @"DS820_SD_600_0101.cwd",
                      @"DS820_PP_600_0101.cwd",
                      @"DS820_SD_610_0101.cwd",
                      @"DS820_PP_610_0101.cwd",
                      @"DS820_SD_620_0101.cwd",
                      @"DS820_PP_620_0101.cwd",
                      ];
            hashes = @[
                       @"549a4983a702db2598d298fced40bd4cec3a36142f6df103be8c69152343601a00",
                       @"ad18947fd24b1260e60ac1bc7fd9a0926948c5bc1d5be9d634aff79490bf102d00",
                       @"85f45be1c691dd52e357112d84d7e29546406d4a0047ea11469a8847925b60f000",
                       @"5439b7d2e015681a8a35a87cea2cd009883ad9f1ccdda3be4ea0061c20051a9e00",
                       @"cfe0171816c22a70e5cb53ef43a87e8e65cf6e0a12003698c96599f2467a526000",
                       @"9d58af57a8a5c482eda70549155802579a287f25c3f798d6e23729c8d2ebad9300",
                       @"6411439215e944833eece0d18ddd58da4238f2ab1a403c4a3dfd4d133c5a58bb00",
                       @"eeda647c3de00fd61b31752dd6175f941e84fe31d9d24565c5f1ddb231008a1d00",
                       @"a7aa33dfbcbc10596f8adf20fef549e6cffba91d76e2251acdcd7c3b8c6fc7ea00",
                       ];
            break;
        default:
            break;
            
    }
    if(files) {
        self.filenames = files;
        self.deviceInfoDict[cFirmwareFileHashes] = hashes ?: @[];
        int i = 0;
        for (name in files) {
            type = [[NSBundle mainBundle] pathForResource:[name stringByDeletingPathExtension] ofType:[name pathExtension]];
            data = [NSData dataWithContentsOfFile:type];
//            NSLog(@"got data of length %lu for file at path %@",(unsigned long)data.length,name);
            if(data) {
                NSString * hash = [self verifyBlob:data];
//                NSLog(@"verifying %@:%@",name,hash);
                didVerify = (hashes.count > i && [hashes[i] isEqualToString:hash?: @""]);
                self.deviceInfoDict[name] = @(didVerify);
                if(!didVerify) {
                    break;
                }
            }
            i++;
        }
    }
    return didVerify;
}
-(BOOL)verifySRecData:(NSData *)data
{
    BOOL isValid = NO;
    if(data.length) {
        BOOL isLineValid;
        const char * bytes = data.bytes;
        char hex[4]={0,0,0,0};
        long byteCount,h;
        NSUInteger lineLength;
        
        if(data.length && bytes[data.length-1] != '\0') {
            NSMutableData * m_data = [NSMutableData dataWithData:data];
            [m_data setLength:m_data.length+1];
            ((char*)m_data.mutableBytes)[m_data.length] = '\0';
            bytes = m_data.bytes;
        }
        NSArray * a = [[NSString stringWithUTF8String:bytes] componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
        for (NSString * line in a) {
            isValid = YES;
            isLineValid = NO;
            lineLength = [line lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
            if(0 == lineLength) {
                //splitting may cause null strings
                continue;
            }
            
            // check each line for proper length and checksum (great place to use GCD)
            // "^S[0-9][0-9A-fa-f]{6,}$"
            bytes = [line UTF8String];
            if(bytes[0]=='S' && !('0' > bytes[1] || '9' < bytes[1])) {
                // verify the byte count for this line
                hex[0]=bytes[2];
                hex[1]=bytes[3];
                h = strtol(hex, NULL, 16);
                if((lineLength-4)/2 == h) {
                    // now add up all the remaining hexpairs and verify the checksum
                    int i=4;
                    byteCount = 0;
                    lineLength--;
                    while(i<lineLength) {
                        byteCount += h;
                        hex[0]=bytes[i++];
                        hex[1]=bytes[i++];
                        h = strtol(hex, NULL, 16);
                    }
                    if(h == (~byteCount & 0xFF)) {
                        isLineValid = YES;
                    }
                }
            }
            if(NO == isLineValid) {
                isValid = NO;
                break;
            }
        }
    }
    return isValid;
}
-(BOOL)verifyMotorolaSFile:(id)filepathOrData {
    NSData * data = nil;
    if([filepathOrData isKindOfClass:[NSData class]])
    {
        data = filepathOrData;
    }
    else if ([filepathOrData isKindOfClass:[NSURL class]])
    {
        data = [NSData dataWithContentsOfURL:filepathOrData];
    }
    else if ([filepathOrData isKindOfClass:[NSString class]])
    {
        data = [NSData dataWithContentsOfFile:filepathOrData];
    }
    
    return [self verifySRecData:data];
}
-(NSData *)paddedData:(NSData *)data {
    // must be padded to 4-byte boundary
    int padSize = 4;
    if(data.length && data.length % padSize)
    {
        NSMutableData * m_d = [NSMutableData dataWithData:data];
        [m_d setLength:data.length + (padSize - data.length % padSize)];
        db("padding data of length %u to length %u",(unsigned)data.length, (unsigned)m_d.length);
        data = [NSData dataWithData:m_d];
    }
    return data;
}
-(NSData *)firmwareBlobForKey:(NSString *) key {
    // must be padded to 4-byte boundary
    NSData * firmware = self.deviceInfoDict[key];
    return [self paddedData:firmware];
}
-(void)setFilenames:(NSArray*)files {
    dispatch_async(dispatch_get_main_queue(), ^{
        [self willChangeValueForKey:@"newFirmwareVersion"];
        self.deviceInfoDict[cFirmwareFilenames] = files;
        [self didChangeValueForKey:@"newFirmwareVersion"];
    });
}
-(NSData *)firmwareBlob {
    NSArray * a = self.deviceInfoDict[cFirmwareFilenames];
    NSString * key = (a.count >0 ) ? a[0] :nil;
    NSString* path = [NSBundle.mainBundle pathForResource:key.stringByDeletingPathExtension ofType:key.pathExtension];
    NSData* firmware = [NSData dataWithContentsOfFile:path];
    
    if(NO == [self verifyMotorolaSFile:firmware]) {
        //bad firmware file
        db("Bad firmware file! Verification failed!");
        firmware = nil;
    }
    
    return [self paddedData:firmware];
}
-(NSString *)newFirmwareVersion {
    NSArray * a = self.deviceInfoDict[cFirmwareFilenames];
    if(nil == a){
        [self verifyFirmwareArchive];
        a = self.deviceInfoDict[cFirmwareFilenames];
    }
    NSString * key = (a.count > 0) ? a[0] : nil;
    key = key.stringByDeletingPathExtension;
    return key;
}
//-(NSData *)cwd300Version {
//    NSArray * a = self.deviceInfoDict[cFirmwareFilenames];
//    NSString * key = (a.count >1 ) ? a[1] :nil;
//    key = key.uppercaseString;
//    return [self paddedData:[NSData dataWithBytes:key.UTF8String length:key.length]];
//}
//-(NSData *)cwd600Version {
//    NSArray * a = self.deviceInfoDict[cFirmwareFilenames];
//    NSString * key = (a.count >2 ) ? a[2] :nil;
//    key = key.uppercaseString;
//    return [self paddedData:[NSData dataWithBytes:key.UTF8String length:key.length]];
//}
//-(NSData *)cwd300Blob {
//    NSArray * a = self.deviceInfoDict[cFirmwareFilenames];
//    NSString * key = (a.count >1 ) ? a[1] :nil;
//    return [self firmwareBlobForKey:key];
//}
//-(NSData *)cwd600Blob {
//    NSArray * a = self.deviceInfoDict[cFirmwareFilenames];
//    NSString * key = (a.count >2) ? a[2] :nil;
//    return [self firmwareBlobForKey:key];
//}
-(UInt16)checksumForData:(NSData*)data
{
    UInt32 count = 0;
    UInt32 bcount = 0;
    const UInt8 * bytes = data.bytes;
    const SInt8 * sytes = data.bytes;
    NSUInteger length = data.length;
    while (length)
    {
        count += bytes[--length];
        bcount += sytes[length];
    }
    db("ucount = %u;scount = %d",(unsigned)count,(int)bcount);
    count = ~count;
    bcount = ~bcount;
    db("u16count = %u;s16count = %d",(unsigned)count,(int)bcount);
    count &= 0xFFFF;
    bcount &= 0xFFFF;
    db("u16count ~= %.4x;s16count ~= %.4x",(UInt16)count,(SInt16)bcount);
    return count;
}
-(void)waitForPrinterStatusCode:(NSString *)waitCode
{
    [self openInterface:^(kern_return_t kr) {
        if(kIOReturnSuccess == kr) {
            [self getPrinterStatus:^(NSString *statusString) {
                if (![[self stringForDSStatusString:statusString] isEqualToString:[self stringForDSStatusString:waitCode]]) {
                    usleep(500000);
                    [self waitForPrinterStatusCode:waitCode];
                }
            }];
        } else {
            usleep(500000);
            [self waitForPrinterStatusCode:waitCode];
        }
    }];
//    while(!(kUSBInterfaceIsOpen == self.state || (kIOReturnSuccess == [self openInterface]))) {
//        usleep(500000);
//    }
//    [self updatePrinterStatus];
//    waitCode = [self stringForDSStatusString:waitCode];
//    while (NO == [waitCode isEqualToString:self.printerStatus])
//    {
//        usleep(500000);
//        [self updatePrinterStatus];
//    }
}
-(double)installedFirmwareVersion {
    NSArray * a = [self.firmwareVersion componentsSeparatedByString:@" "];
    NSString * val = a.lastObject;
    return  val.doubleValue;
}
-(double)availableFirmwareVersion {
    NSArray * a = [self.newFirmwareVersion componentsSeparatedByString:@" "];
    NSString * val = a.lastObject;
    return  val.doubleValue;
}
-(void)setFirmwareUpdateStatus:(int)flag {
    
    dispatch_async(dispatch_get_main_queue(), ^{
        NSString * key = self.keyUSBdevice;
        id object = self.deviceInfoDict;
        self.deviceInfoDict[deviceFirmwareUpdateState] = @(flag);
        db("writing key:%@ for dict:%@",key, object);
        [[self class] setDeviceInfo:object forKey:key];
    });
}
//- (IOReturn)reEnumerateUSBDevice {
//    // use this override for testing purposes
//    [self finishFirmwareUpdate];
//    return 0;
//}
-(void)updateFirmwareAndColorTables
{
    if(nil == self.firmwareVersion) {
        [self reEnumerateUSBDevice];
        return;
    }
    if(NO == [self verifyFirmwareArchive] || nil == self.firmwareBlob ) {
        [self showAlertWithTitle:@"-- File corruption --\n\nThere is a problem with the firmware files." message:@"Contact customer support." buttonTitles:nil completion:nil];
    } else {
        dispatch_async(dispatch_get_main_queue(), ^{
            // must be done on mainQ
            self.inFirmwareUpdate = YES;
        });
        [self openInterface:^(kern_return_t kr) {
            if(kIOReturnSuccess == kr) {
                
                // wait for status to indicate ready mode
                [self waitForPrinterStatusCode:@"00000"];
                
                // set firmware update mode
                self.overridePrinterStatus = @"Updating firmware ...";

//                self.deviceInfoDict[ATMPPR_TBPrinterManufacturer] = self.manufacturer;
//                self.deviceInfoDict[ATMPPR_TBPrinterModel] = self.model;
                self.deviceInfoDict[ATMPPR_TBPrinterFirmwareVersionPrevious] = self.firmwareVersion;
                self.deviceInfoDict[deviceFirmwareUpdateState] = @(k_dnp_firmwareWillUpdate);
                
                // we are on the printer command queue
                [self sendPrinterCommand:DNP_COMMAND_SET_MODE_FIRMWARE_WRITE withPayload:nil];
                
                [self closeInterface:nil];
            }
        }];
    }
}
-(NSString*)RX1UpdateWarningText {
    double versionCurrent = [[self.firmwareVersion componentsSeparatedByString:@" "].lastObject doubleValue];
    NSString* version = NSBundle.mainBundle.infoDictionary[@"CFBundleShortVersionString"];
    return [NSString stringWithFormat:@"This update tool is for the DNP RX1 printer. It will upgrade the printer to %@.\n\nOnce updated, the RX1 printer will REQUIRE the use of RX1HS media packs.%s This process cannot be undone.",version, versionCurrent < 2.04 ? " Please use up all RX1 media before using this update tool.":""];
}
-(NSString*)genericUpdateWarningText {
    return [NSString stringWithFormat:@"This update tool will upgrade the printer firmware to version %@. This process cannot be undone",self.newFirmwareVersion];
}
-(void)old_showAlertWithTitle:(NSString*)title message:(NSString*)message buttonTitles:(NSArray<NSString*>*)buttonTitles completion:(void (^ _Nullable)(NSModalResponse returnCode))replyBlock {
    NSLog(@"%s",__func__);
    if(title.length || message.length) {
        title = title.copy;
        message = message.copy;
        buttonTitles = buttonTitles.copy;
        dispatch_async(dispatch_get_main_queue(), ^{
            NSAlert * alert = [[NSAlert alloc] init];
            alert.icon = self.image;
            alert.messageText = title ?: @"Alert";
            alert.informativeText = message ?: @"";
            
            if(buttonTitles.count) {
                for (NSString* name in buttonTitles) {
                    [alert addButtonWithTitle:name];
                }
                
            } else {
                [alert addButtonWithTitle:@"OK"];
            }
            [alert beginSheetModalForWindow:NSApp.mainWindow completionHandler:replyBlock];
        });
    }
}
-(void)showAlertWithTitle:(NSString*)title message:(NSString*)message buttonTitles:(NSArray<NSString*>*)buttonTitles completion:(void (^ _Nullable)(NSModalResponse returnCode))replyBlock {
    [self showCustomAlertWithTitle:title ?: @"Alert" message:message ?: @"" buttonTitles:buttonTitles ?: @[@"OK"] accessoryView:nil alertStyle:NSWarningAlertStyle customLayout:nil completion:replyBlock];
}
-(void)showCustomAlertWithTitle:(NSString*)title message:(NSString*)message buttonTitles:(NSArray<NSString*>*)buttonTitles accessoryView:(nullable NSView*)accessoryView alertStyle:(NSAlertStyle)style customLayout:(void (^ _Nullable)(NSAlert* alert))layoutBlock completion:(void (^ _Nullable)(NSModalResponse returnCode))replyBlock {
    NSLog(@"%s",__func__);
    if(title.length || message.length) {
        NSAlert * alert = [[NSAlert alloc] init];
        alert.icon = self.image;
        alert.messageText = title ?: @"Alert";
        alert.informativeText = message ?: @"";
        alert.accessoryView = accessoryView;
        alert.alertStyle = style;
        
        if(buttonTitles.count) {
            for (NSString* name in buttonTitles) {
                [alert addButtonWithTitle:name];
            }
        } else {
            [alert addButtonWithTitle:@"OK"];
        }
        dispatch_async(dispatch_get_main_queue(), ^{
            if(layoutBlock) {
                [alert layout];
                layoutBlock(alert);
            }
            [alert beginSheetModalForWindow:NSApp.mainWindow completionHandler:replyBlock];
        });
    }
}
-(void)setToggleAgreed:(id)sender {
    if([sender isKindOfClass:NSButton.class]) {
        NSButton* button = sender;
        [self willChangeValueForKey:@"customerDidAgree"];
        _customerDidAgree = (button.state == NSOnState);
        [self didChangeValueForKey:@"customerDidAgree"];
    }
}
-(BOOL)customerDidAgree{
    return _customerDidAgree;
}
-(void)ShowRX1Warning {
    NSButton* buttonConfirm = [[NSButton alloc] initWithFrame:NSMakeRect(0, 0, 72, 18)];
    buttonConfirm.title = @"I Agree";
    _customerDidAgree = NO;
    buttonConfirm.buttonType = NSSwitchButton;
    buttonConfirm.state = NSOffState;
    buttonConfirm.target = self;
    buttonConfirm.action = @selector(setToggleAgreed:);
    NSString* message = nil;
    switch (_idProduct) {
        case k_dnp_product_id_RX1:
            message = self.RX1UpdateWarningText;
            break;
            
        default:
            message = self.genericUpdateWarningText;
            break;
    }

    [self showCustomAlertWithTitle:@"Attention:" message:message buttonTitles:@[@"Quit",@"Continue"] accessoryView:buttonConfirm alertStyle:NSCriticalAlertStyle customLayout:^(NSAlert *alert) {
        //make the background Red and the textbox background yellow
        NSButton* buttonContinue = alert.buttons.count > 1 ? alert.buttons[1] : nil;
        if([buttonContinue isKindOfClass:NSButton.class]) {
            [self.class exposeBinding:@"customerDidAgree"];
            [buttonContinue bind:@"enabled" toObject:self withKeyPath:@"customerDidAgree" options:nil];
        }
    } completion:^(NSModalResponse returnCode) {
        // if we have both confirmation then proceed
        if(NSAlertSecondButtonReturn == returnCode && buttonConfirm.state == NSOnState) {
            [self proceedWithUpdateWhenReady];
        } else if (NSAlertFirstButtonReturn == returnCode) {
            // terminate
            [NSApp terminate:self];
        }
    }];
}
-(void)updateColorTables:(void (^ _Nullable)(BOOL didUpdate))replyBlock  {
    /*
     1 <ESC>P STATUS
     2 <ESC>P TBL_CL
     3 <ESC>P TBL_WT Version
     4 <ESC>P TBL_WT CTRLD_UPDATE_CW
     5 <ESC>P TBL_RD CWD300_Version 00000004 nnnn
     6 <ESC>P TBL_RD CWD300_Checksum 00000004 nnnn
     7 <ESC>P TBL_WT Version
     8 <ESC>P TBL_WT CTRLD_UPDATE_CW
     9 <ESC>P TBL_RD CWD600_Version 00000004 nnnn
     10 <ESC>P TBL_RD CWD600_Checksum 00000004 nnnn
     11 <ESC>P TBL_WT Version
     12 <ESC>P TBL_WT CTRLD_UPDATE_CW
     13 <ESC>P TBL_RD CWD610_Version 00000004 nnnn
     14 <ESC>P TBL_RD CWD610_Checksum 00000004 nnnn
     15 <ESC>P TBL_WT Version
     16 <ESC>P TBL_WT CTRLD_UPDATE_CW
     17 <ESC>P TBL_RD CWD620_Version 00000004 nnnn
     18 <ESC>P TBL_RD CWD620_Checksum 00000004 nnnn
     */

    if(NO == [self verifyFirmwareArchive]) {
        db("failed to verify firmware archive");
    } else {
        [self openInterface:^(kern_return_t kr) {
            
            // we are on the printer command queue
            
            BOOL didUpdate = NO;
            if(kIOReturnSuccess == kr) {
                
                // wait for status to indicate ready mode
                [self waitForPrinterStatusCode:@"00000"];

                self.overridePrinterStatus = @"Updating color tables ...";
                
                // clear the color table
                [self sendPrinterCommand:DNP_COMMAND_SET_COLOR_TABLE_CLEAR withPayload:nil];
                
                [self getPrinterColorTableVersion:nil];
                [self getPrinterColorTableChecksum:nil];

                for (NSString* name in self.colorTableNames) {
                    NSString* key = name.uppercaseString;
                    NSData* versionData = [self paddedData:[NSData dataWithBytes:key.UTF8String length:key.length]];
                    NSData* colorData = [self paddedColorDataForVersion:name];
                    [self sendPrinterCommand:DNP_COMMAND_SET_COLOR_TABLE_VERSION withPayload:versionData];
                    [self sendPrinterCommand:DNP_COMMAND_SET_COLOR_TABLE_DATA withPayload:colorData];
                    didUpdate = YES;
                    [self getPrinterColorTableVersion:nil];
                    [self getPrinterColorTableChecksum:nil];
                }
                self.deviceInfoDict[deviceFirmwareUpdateState] = @(k_dnp_firmwareDidFinish);
                if(replyBlock) {
                    replyBlock(didUpdate);  // send this on the command block so it waits until all the commands are sent
                }
            }
        }];
    }
}
-(nullable NSData*)paddedColorDataForVersion:(NSString*)version {
    NSString* path = [NSBundle.mainBundle pathForResource:version.stringByDeletingPathExtension ofType:version.pathExtension];
    NSData* data = [NSData dataWithContentsOfFile:path];
    NSLog(@"got data of length %lu for file at path %@",(unsigned long)data.length,version);
//    NSLog(@"hash:%@",[self asciiHexForData:data]);
    return data;
}
-(NSArray*)colorTableNames {
    NSArray * a = self.deviceInfoDict[cFirmwareFilenames];

    if(a.count > 2) {
        NSMutableArray* a_m = a.mutableCopy;
        [a_m removeObjectAtIndex:0];    // the first item is always the firmware file
        a = a_m.copy;
    } else {
        a = @[];
    }
    return a;
}
-(void)finishFirmwareUpdate {
    NSLog(@"%s",__func__);
    [self updateColorTables:^(BOOL didUpdate) {
        NSLog(@"%s",__func__);
        if(didUpdate) {
            [self showAlertWithTitle:@"Firmware update was successful!" message:[NSString stringWithFormat:@"Firmware version updated from %@ to %@",self.deviceInfoDict[ATMPPR_TBPrinterFirmwareVersionPrevious],self.firmwareVersion] buttonTitles:@[@"Finish"] completion:nil];
        } else {
            [self showAlertWithTitle:@"There was a problem with the firmware update!" message:[NSString stringWithFormat:@"Previous firmware version: %@ current version: %@",self.deviceInfoDict[ATMPPR_TBPrinterFirmwareVersionPrevious],self.firmwareVersion] buttonTitles:nil completion:nil];
        }
        dispatch_async(dispatch_get_main_queue(), ^{
            // must be placed on main_queue here because custom setter is ignored
            self.inFirmwareUpdate = NO;
        });
        [self updatePrinterStatus];
    }];
}
-(void)setOverridePrinterStatus:(NSString*)status {
    NSLog(@"forcing printer status string to \"%@\"",status);
    dispatch_async(dispatch_get_main_queue(), ^{
        // must put this on mainQ since KVO circumvents custom setters
        self.printerStatus = status;
    });
}
-(void)uploadFirmware {
    if(NO == [self verifyFirmwareArchive]) {
        NSLog(@"failed to verify firmware archive");
        [self showAlertWithTitle:@"There was a problem with the firmware file!" message:@"This application may be damaged. Please download a new copy." buttonTitles:nil completion:nil];
    } else {
        [self openInterface:^(kern_return_t kr) {
            
            // we are on the printer command queue
            
            if(kIOReturnSuccess == kr) {
                self.overridePrinterStatus = @"Writing firmware ...";
                NSLog(@"failed to verify firmware archive");

                // send the firmware data
                NSData * firmwareBlob = self.firmwareBlob;
                if(firmwareBlob) {
                    NSLog(@"Uploading firmware to printer");
                    self.deviceInfoDict[deviceFirmwareUpdateState] = @(k_dnp_firmwareDidUpdate);
//                    self.firmwareUpdateStatus = k_dnp_firmwareDidUpdate;
                    [self sendPrinterCommand:DNP_COMMAND_SET_FIRMWARE_UPLOAD withPayload:firmwareBlob];
                } else {
                    // alert bad firmware
                    NSLog(@"FAILED Uploading firmware to printer (bad file)");
                    [self showAlertWithTitle:@"There was a problem with the firmware file!" message:@"This application may be damaged. Please download a new copy." buttonTitles:nil completion:nil];
                }
                [self closeInterface:nil];
                
                // the printer should reenumerate at this point ...
            }
        }];
    }
}
-(void)proceedWithUpdateWhenReady {
    
    // check for device already in FU
    if ([self.printerStatus isEqualToString:[self stringForDSStatusString:@"FU00001"]]) {
        [self uploadFirmware];
        return;
    }
    
    // NOTE!!!
    // before changing the if() test below to enable installing "older" versions  DO THIS INSTEAD:
    //
    // Either
    //      1) set the default for your app using `defaults' (see man defaults), for example:
    //          defaults write com.dnpphoto.DNP-DS620-Firmware-Updater installOlderFirmwareVersion -bool YES
    //
    //      The above options works without recomiling and is the recommended method to use (can be deployed "in the field")
    //
    //      2) temporarily enable the line below, recompile and run, and then promptly disable it again (it needs to be enabled for only one run)
    //
    //    [NSUserDefaults.standardUserDefaults setBool:YES forKey:@"installOlderFirmwareVersion"];
    
    // the line below can be enabled to verify (in debugger) the presence of this defaults key
//        id obj = [NSUserDefaults.standardUserDefaults objectForKey:@"installOlderFirmwareVersion"];
    if(self.installedFirmwareVersion > self.availableFirmwareVersion
        && ![NSUserDefaults.standardUserDefaults boolForKey:@"installOlderFirmwareVersion"]) {
        // show alert that FW update is unecessary
        [self showAlertWithTitle:@"Firmware update not needed" message:[NSString stringWithFormat:@"This printer has firmware version %.2f installed.",self.installedFirmwareVersion] buttonTitles:nil completion:nil];
    } else {
        [self openInterface:^(kern_return_t kr) {
            if(kIOReturnSuccess == kr) {
                [self getPrinterStatus:^(NSString *statusString) {
                    if(![statusString isEqualToString:@"00000"]) {
                        //show alert
                        [self showAlertWithTitle:@"Printer Not Ready" message:[NSString stringWithFormat:@"Please correct the following issue: %@",[self stringForDSStatusString:statusString]] buttonTitles:@[@"Proceed",@"Cancel"] completion:^(NSModalResponse returnCode) {
                            if(NSAlertFirstButtonReturn == returnCode) {
                                [self proceedWithUpdateWhenReady];
                            }
                        }];
                    } else {
                        // we must be ready now
                        [self updateFirmwareReady];
                    }
                }];
            } else if(kIOReturnExclusiveAccess == kr) {
                //show alert
                [self showAlertWithTitle:@"Printer Busy" message:@"Another application is using the printer. Please proceed when the printer becomes available." buttonTitles:@[@"Proceed",@"Cancel"] completion:^(NSModalResponse returnCode) {
                    if(NSAlertFirstButtonReturn == returnCode) {
                        [self proceedWithUpdateWhenReady];
                    }
                }];
            } else {
                // unknown error
                [self showAlertWithTitle:@"Printer Error" message:@"The printer is not responding properly. Please power-cycle the printer and try again." buttonTitles:@[@"Proceed",@"Cancel"] completion:^(NSModalResponse returnCode) {
                    if(NSAlertFirstButtonReturn == returnCode) {
                        [self proceedWithUpdateWhenReady];
                    }
                }];
            }
        }];
    }
}
//-(void)probe { [self finishFirmwareUpdate]; }
-(IBAction)updateFirmware:(id)sender
{
    if(NO == [self verifyFirmwareArchive] || nil == self.firmwareBlob ) {
        [self showAlertWithTitle:@"-- File corruption --\n\nThere is a problem with the firmware files." message:@"Contact customer support." buttonTitles:@[@"Quit"] completion:^(NSModalResponse returnCode) {
            [NSApp terminate:nil];
        }];
    } else {
        [self ShowRX1Warning];
        
//        switch (_idProduct) {
//            case k_dnp_product_id_RX1:
//                break;
//                
//            default:
//                [self proceedWithUpdateWhenReady];
//                break;
//        }
    }
}
-(IBAction)updateFirmwareReady {
    [self showAlertWithTitle:@"-- Warning --\n\nAre you certain you want to update the firmware?" message:@"The printer may be permanently damaged if it is unplugged or loses power during the update process." buttonTitles:@[@"Cancel",[NSString stringWithFormat:@"Update Firmware to %@",self.newFirmwareVersion]] completion:^(NSModalResponse returnCode) {
        if(NSAlertSecondButtonReturn == returnCode) {
            [self updateFirmwareAndColorTables];
        }
    }];
}
-(void)showPowerCycleAlert
{
    // first check to see if we need this
    NSNumber * val = nil; // 2015-06-05steve never suppress [[NSUserDefaults standardUserDefaults] objectForKey:ATMPPR_DNP_USB_SuppressPowerCycleAlert];
    
    if(val.boolValue || (self.usbISerial && self.serialNumberUSB != nil) || ((NO == self.usbISerial && self.serialNumberUSB == nil)))
    {
        return;
    }
    NSAlert * alert = [[NSAlert alloc] init];
    alert.icon = self.image;
    alert.messageText = NSLocalizedString(@"Please power-cycle the selected printer now to complete the process!", "Please power-cycle the selected printer now to complete the process!"); // 2015-06-12steve  @"Please power-cycle this printer now to complete the process!";
    alert.showsSuppressionButton = NO; // 2015-06-05steve YES;
    dispatch_async(dispatch_get_main_queue(), ^{
        [alert runModal];
        if([[alert suppressionButton] state])
        {
            // store this value and check later
            [[NSUserDefaults standardUserDefaults] setObject:@YES forKey:ATMPPR_DNP_USB_SuppressPowerCycleAlert];
        }
    });
}
-(void)showPrinterBusyAlert
{
    NSAlert * alert = [[NSAlert alloc] init];
    alert.icon = self.image;
    alert.messageText = NSLocalizedString(@"The Selected Printer is Unavailable. (Busy)", @"The Selected Printer is Unavailable. (Busy)"); // 2015-06-12  @"The Selected printer is unavailable (busy)!";
    dispatch_async(dispatch_get_main_queue(), ^{
        [alert runModal];
    });
}
-(void)deleteExistingPrinterQueues
{
    system("for q in $(lpstat -v|awk '/DP-DS620[?]/ {print $3}');do lpadmin -x ${q%:};done");
}
-(void)setEnabledUSBSerial:(BOOL)enabled
{
    [self openInterface:^(kern_return_t kr) {
        if(kIOReturnSuccess == kr) {
            [self setUSB_ISerial:enabled];
//            [self getUSB_ISerial];
            
            [self closeInterface:^(kern_return_t kr) {
//                dispatch_async(dispatch_get_main_queue(), ^{
                    [self showPowerCycleAlert];
//                });
                [self deleteExistingPrinterQueues];
            }];
        } else {
            [self showPrinterBusyAlert];
        }
    }];
}
//-(IBAction)enableUSBSerial:(id)sender {
//    [self setEnabledUSBSerial:YES];
//}
//-(IBAction)disableUSBSerial:(id)sender {
//    [self setEnabledUSBSerial:NO];
//}
-(NSData *)printerReply
{
    NSData * replyData = nil;
    IOReturn ioResult;
    UInt32 noDataTimeout = 5000;
    UInt32 completionTimeout = 5000;
    UInt32 byteCount;

    // this printer sends 8 bytes of reply to indicate the size (in ascii decimal digits!) of the following response
    NSMutableData * m_data = [NSMutableData dataWithLength:256];
    char * mBytes = m_data.mutableBytes;
    IOUSBInterfaceInterface300 ** interface = self.interfaceInterface;

    byteCount = 8;
    
    // read 8 bytes and get a reply
    _readStatus = ioResult = (*interface)->ReadPipeTO(interface, _bulkIN, mBytes, &byteCount, noDataTimeout, completionTimeout);
    db("reading backchannel for %u bytes on %s",(unsigned)byteCount,self.descriptionUTF8);
    if (kIOReturnSuccess == ioResult) {
        //calculate the reply size and read the rest of it
        mBytes[8]='\0';
        byteCount = (UInt32)strtol(mBytes, NULL, 10);
        // this printer never replies much
        if(4096 < byteCount) {
            //something very strange indeed ...
            [self errorWithString:[NSString stringWithFormat:NSLocalizedString(@"Internal error L%d",""),__LINE__] code:0];
            return nil;
        }
        if(m_data.length < byteCount + 1) {
            [m_data setLength:byteCount + 1];
        }
        _readStatus = ioResult = (*interface)->ReadPipeTO(interface, _bulkIN, mBytes, &byteCount, noDataTimeout, completionTimeout);
        if (kIOReturnSuccess == ioResult) {
            mBytes[byteCount] = '\0';
            
            int i;
            for(i = 0;i < byteCount && mBytes[i] != 0x0D; i++); // this line increments i to the first 0x0D
            if(i < byteCount)
            {
                //truncate the reply after the first 0x0D
                [m_data setLength:i+1];
                mBytes[i] = '\0';
            }
            replyData = [NSData dataWithData:m_data];
        }
        else {
            db("bad read (%s) on backchannel of %s",strForIOReturn(ioResult),self.descriptionUTF8);
        }
    }
    else {
        db("bad read (%s) on backchannel of %s",strForIOReturn(ioResult),self.descriptionUTF8);
    }
//    LOG_DATA(replyData);
    return replyData;
}
#pragma mark getters
-(NSString *)colorTableVersion {
    return  self.deviceInfoDict[ATMPPR_TBPrinterColorTableVersion];
}
-(NSString *)colorTableVersion600 {
    return  self.deviceInfoDict[ATMPPR_TBPrinterColorTableVersion600];
}
-(NSString *)colorTableVersion610 {
    return  self.deviceInfoDict[ATMPPR_TBPrinterColorTableVersion610];
}
-(NSString *)colorTableVersion620 {
    return  self.deviceInfoDict[ATMPPR_TBPrinterColorTableVersion620];
}
-(NSString *)colorTableChecksum300 {
    return  self.deviceInfoDict[ATMPPR_TBPrinterColorTableChecksum300];
}
-(NSString *)colorTableChecksum600 {
    return  self.deviceInfoDict[ATMPPR_TBPrinterColorTableChecksum600];
}
-(NSString *)colorTableChecksum610 {
    return  self.deviceInfoDict[ATMPPR_TBPrinterColorTableChecksum610];
}
-(NSString *)colorTableChecksum620 {
    return  self.deviceInfoDict[ATMPPR_TBPrinterColorTableChecksum620];
}
-(NSString *)colorTableVersion300HD {
    return  self.deviceInfoDict[ATMPPR_TBPrinterColorTableVersion300HD];
}
-(NSString *)colorTableVersion600HD {
    return  self.deviceInfoDict[ATMPPR_TBPrinterColorTableVersion600HD];
}
-(NSString *)colorTableVersion610HD {
    return  self.deviceInfoDict[ATMPPR_TBPrinterColorTableVersion610HD];
}
-(NSString *)colorTableVersion620HD {
    return  self.deviceInfoDict[ATMPPR_TBPrinterColorTableVersion620HD];
}
-(NSString *)colorTableChecksum300HD {
    return  self.deviceInfoDict[ATMPPR_TBPrinterColorTableChecksum300HD];
}
-(NSString *)colorTableChecksum600HD {
    return  self.deviceInfoDict[ATMPPR_TBPrinterColorTableChecksum600HD];
}
-(NSString *)colorTableChecksum610HD {
    return  self.deviceInfoDict[ATMPPR_TBPrinterColorTableChecksum610HD];
}
-(NSString *)colorTableChecksum620HD {
    return  self.deviceInfoDict[ATMPPR_TBPrinterColorTableChecksum620HD];
}
-(void)setUsbISerial:(BOOL)enable
{
    NSString *string = self.deviceInfoDict[ATMPPR_DNP_USB_ISerialisSet];
    if(enable != string.boolValue) {
        // make the appropriate change
        self.enabledUSBSerial = enable;
//        if(enable) {
//            [self enableUSBSerial:self];
//        } else {
//            [self disableUSBSerial:self];
//        }
    }
}
-(BOOL)usbISerial
{
    NSString *string = self.deviceInfoDict[ATMPPR_DNP_USB_ISerialisSet];
    return  string.boolValue;
}
-(NSString *)usbISerialString {
    return self.hasISerialSupport ? (self.usbISerial ? @"Enabled" : @"Disabled") : @"Unavailable";
}
-(NSString *)lifetimeCounter
{
    NSString * lifetimeCounter = self.deviceInfoDict[ATMPPR_TBPrinterLifetimeCounter];
    return lifetimeCounter;
}
-(NSImage *)image
{
    NSString * imageName = nil;
    switch (_idProduct) {
        case k_dnp_product_id_DS40:
        case k_dnp_product_id_DS80:
            imageName = @"DNP_DS_Series";
            break;
            
        case k_dnp_product_id_RX1:
            imageName = @"DNP_RX_Series";
            break;
            
        case k_dnp_product_id_DS620:
            imageName = @"D620A_LSide";
            break;
            
        case k_dnp_product_id_DS820:
            imageName = @"DS820";
            break;
            
        default:
            break;
    }
    return [NSImage imageNamed:imageName];
}
-(void)probeAtInit {
    
    [self openInterface:^(kern_return_t kr) {
        if(kIOReturnSuccess == kr) {
            
//            [self updatePrinterStatus];
//            [self getPrinterFirmwareVersion];
            
            // check for firmware update in progress
            NSNumber * state = self.deviceInfoDict[deviceFirmwareUpdateState];
            if(state) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    self.inFirmwareUpdate = YES;
                });
                NSString* firmwareString = self.inlineGetFirmwareString;
                self.deviceInfoDict[ATMPPR_TBPrinterFirmwareVersion] = firmwareString;
                NSLog(@"printer firmware is currently reported as \"%@\"",firmwareString);
//                [self getPrinterFirmwareVersion];
                // we should check if we are in process of upgrading FW
                switch (state.intValue) {
                    case k_dnp_firmwareWillUpdate:
                    {
                        db("found a device waiting for firmware update");
                        // check for firmware update mode FU00001
                        [self getPrinterStatus:^(NSString *statusString) {
                            if ([statusString isEqualToString:@"FU00001"]) {
                                // write firmware
                                db("device is in firmware update mode ... uploading firmware");
                                [self uploadFirmware];
                                // this command should close the interface
                            } else {
                                NSLog(@"Bad Status (%@) while waiting for firmware update",statusString);
                            }
                        }];
                        break;
                    }
                    case k_dnp_firmwareDidUpdate:
                    {
                        db("found a device rebooting after firmware update");
                        //compare versions
                        NSString * priorVersion = self.deviceInfoDict[ATMPPR_TBPrinterFirmwareVersionPrevious];
                        NSString * currentVersion = self.deviceInfoDict[ATMPPR_TBPrinterFirmwareVersion];
                        currentVersion = firmwareString;
                        if([firmwareString isEqualToString:@"RX1_ALLFWU_2.05"]) {
                            // do nothing
                            sleep(1);
                            break;
                        }
                        if(priorVersion && NO == [priorVersion isEqualToString:currentVersion]) {
                            db("firmware version changed from %@ to %@",priorVersion,currentVersion);
                        } else {
                            db("firmware version unchanged at %@",currentVersion);
                        }
                        self.deviceInfoDict[ATMPPR_TBPrinterFirmwareVersion] = currentVersion;
                        [self finishFirmwareUpdate];
                        break;
                    }
                    default:
                    case k_dnp_firmwareDidFinish:
                    {
                        db("found a device finished with firmware update");
                        // after writing color tables
                        dispatch_async(dispatch_get_main_queue(), ^{
                            self.inFirmwareUpdate = NO;
                        });
                        [self.deviceInfoDict removeObjectForKey: deviceFirmwareUpdateState];
                        [self getDeviceCustomInfo];
                        break;
                    }
                }
                [self closeInterface:nil];
            } else {
                // check for firmware update mode FU00001
                [self getPrinterStatus:^(NSString *statusString) {
                    if ([statusString isEqualToString:@"FU00001"]) {
                        // unexpectedly found a device in firmware update mode
                        [self showCustomAlertWithTitle:@"Attention:" message:@"A printer was unexpectedly found in firmware update mode. Would you like to update the firmware? " buttonTitles:@[@"Ignore",@"Update Firmware"] accessoryView:nil alertStyle:NSCriticalAlertStyle customLayout:nil completion:^(NSModalResponse returnCode) {
                            // if we have both confirmation then proceed
                            if(NSAlertSecondButtonReturn == returnCode) {
                                db("device is in firmware update mode ... uploading firmware");
                                [self uploadFirmware];
                                // this command should close the interface
                            } else {
                                [self showAlertWithTitle:@"Please power-cycle this printer" message:@"Contact customer support if power-cycling does not resolve the issue." buttonTitles:nil completion:nil];
                            }
                        }];
                    } else {
                        [self getDeviceCustomInfo];
                    }
                }];

            }
        }
    }];
}
//-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
//    db("new value %@ for keypath %@",change,keyPath);
//    if([keyPath isEqualToString:ATMPPR_TBPrinterStatus]) {
//    }
//}
- (id)initWithInterfaceInterface:(IOUSBInterfaceInterface300 **)obj forDeviceObject:(io_service_t)devObject {
    if (self = [super initWithInterfaceInterface:obj forDeviceObject:(io_service_t)devObject])
    {
        // check for an existing infodict
        NSLog(@"++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
        NSLog(@"++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
        NSLog(@"++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
        db("initing device for key %@",self.keyUSBdevice);
        NSMutableDictionary * infoDict = [self.class deviceInfoForKey:self.keyUSBdevice];
        if(infoDict)
        {
            NSLog(@"infoDict here is %@",infoDict);// 2013-02-27steve
            
            [self.deviceInfoDict addEntriesFromDictionary:infoDict];
            dispatch_async(dispatch_get_main_queue(), ^{
                // put on main queue to see if it helps resolve spurious binding error
                NSString * value;
                if( !self.manufacturer && (value = [infoDict objectForKey:ATMPPR_TBPrinterManufacturer]) ) {
                    self.manufacturer = value;
                }
                if(!self.model && (value = [infoDict objectForKey:ATMPPR_TBPrinterModel])) {
                    self.model = value;
                }
            });
#ifdef _DNP_FIRMWARE_UPDATER
            // check if we are in firmware update mode
            [self probeAtInit];
#endif
        }
    }
    return self;
}
//- (void)dealloc
//{
//    NSLog(@"%s",__func__);
//    [self.deviceInfoDict setObject:self.manufacturer forKey:ATMPPR_TBPrinterManufacturer];
//    [self.deviceInfoDict setObject:self.model forKey:ATMPPR_TBPrinterModel];
//    [[self class] setDeviceInfo:self.deviceInfoDict forKey:self.keyUSBdevice];
//
//    [super dealloc];
//}
@end
